"""
Standalone script meant to simplify running all validation routines from the
command line.

.. program-output:: python "{__file__}" -h

------------
``validate``
------------

.. program-output:: python "{__file__}" validate -h

"""

__doc__ = __doc__.format(__file__=__file__)

import datetime
import logging
from argparse import ArgumentParser
from collections.abc import Callable
from enum import IntEnum
from functools import wraps
from pathlib import Path
from typing import Protocol, Self, TypeVar

from ng911ok.lib.iterablenamespace import FrozenList
from ng911ok.lib.misc import indent
from ng911ok.lib.session import config
# noinspection PyProtectedMember
from ng911ok.lib.validator import ValidationRoutine, NG911Validator, _ValidationRoutineProxy, ValidationPrerequisiteError


class ExitCode(IntEnum):
    SUCCESS = 0
    """Program completed successfully. If returned by the ``validate`` subcommand, this code indicates that the geodatabase passed validation."""

    UNCAUGHT_EXCEPTION = 10
    """Indicates an unexpected exception was raised."""

    # BAD_SUBCOMMAND = 11
    # """Indicates that no subcommand or an invalid subcommand was given."""
    #
    # SUBCOMMAND_ERROR = 12
    # """Indicates an error with arguments passed to the subcommand."""

    VALIDATION_FAIL = 20
    """Returned by the ``validate`` subcommand, indicating that the geodatabase failed validation."""

    VALIDATION_FAIL_EARLY_EXIT = 21
    """Returned by the ``validate`` subcommand, indicating that the geodatabase failed validation and the ``--exit-early`` flag was used."""


class Args(Protocol):
    func: Callable[[Self], ExitCode]


class ValidateArgs(Args):
    gdb: Path
    export: bool
    ignore_submit: bool
    exit_early: bool


SubcommandArgs = TypeVar("SubcommandArgs", bound=Args, covariant=True)

def subcommand(func: Callable[[SubcommandArgs], ExitCode]) -> Callable[[SubcommandArgs], ExitCode]:
    """
    Wraps *func* in a ``try`` block. If an uncaught exception is raised within
    *func*, the decorated function will return ``10`` indicating this. If
    *func* succeeds, the exit code it returns will be passed through.

    :param Callable func: Subcommand function
    :return Callable: Wrapped subcommand function
    """
    @wraps(func)
    def _subcommand(args: SubcommandArgs) -> ExitCode:
        try:
            exit_code = func(args)
        except Exception as exc:
            logger.fatal("Uncaught exception.", exc_info=exc)
            print(f"Uncaught exception. This is a bug. If requesting support, please include the file 'ng911ok.log' ({Path(__file__).absolute().parent / 'ng911ok.log'}).")
            exit_code = ExitCode.UNCAUGHT_EXCEPTION
        else:
            logger.info(f"Successfully ran subcommand function '{func.__name__}'; got exit code {exit_code}.")
        return exit_code
    return _subcommand


# @subcommand
# def no_subcommand_given(args: Args) -> ExitCode:
#     print("No subcommand given. Run this script with the -h flag for help.")
#     return ExitCode.BAD_SUBCOMMAND


@subcommand
def validate(args: ValidateArgs) -> ExitCode:
    """Implementation for the ``validate`` subcommand."""
    with NG911Validator(
        workspace=args.gdb,
        respect_submit=not args.ignore_submit,
        use_edit_session=False,
        export=args.export,
        overwrite_error_tables=args.export
    ) as validator:
        routines: FrozenList[ValidationRoutine] = ValidationRoutine.get_routines()
        for routine in routines:
            proxy: _ValidationRoutineProxy = routine.__get__(validator, type(validator))
            logger.debug(f"Running routine '{routine.name}'.")
            if routine.takes_feature_class_argument:
                for fc in config.required_feature_class_names:
                    try:
                        proxy(fc, reraise=True)
                    except ValidationPrerequisiteError as exc:
                        logger.info(f"Validation prerequisite failed (feature class: '{fc}').", exc_info=exc)
                        if args.exit_early:
                            return ExitCode.VALIDATION_FAIL_EARLY_EXIT
            else:
                try:
                    proxy(reraise=True)
                except ValidationPrerequisiteError as exc:
                    logger.info("Validation prerequisite failed.", exc_info=exc)
                    if args.exit_early:
                        return ExitCode.VALIDATION_FAIL_EARLY_EXIT

            if args.exit_early and validator.has_errors:
                return ExitCode.VALIDATION_FAIL_EARLY_EXIT

        if issues := validator.issue_summary:
            code_width = max(map(len, issues.keys()))
            count_width = max(map(len, map(str, issues.values())))
            code_count_str = "\n".join(f"{k:{code_width}} | {v:{count_width}}" for k, v in issues.items())
            message = f"==== ISSUE SUMMARY ====\n{indent(code_count_str)}"
            logger.info(message)
            print(message)

        return ExitCode.VALIDATION_FAIL if validator.has_errors else ExitCode.SUCCESS


def main():
    parser = ArgumentParser("quickvalidate.py")
    # parser.set_defaults(func=no_subcommand_given)
    subparsers = parser.add_subparsers(title="Subcommands", required=True)

    validate_parser = subparsers.add_parser("validate", aliases=["v"], help="Attempts to run all validation routines on an NG911 geodatabase")
    validate_parser.set_defaults(func=validate)
    validate_parser.add_argument("gdb", type=Path, help="Path to the NG911 geodatabase to validate")
    validate_parser.add_argument("--export", action="store_true", help="Whether to export and overwrite error tables if validation generates issues")
    validate_parser.add_argument("--ignore-submit", action="store_true", help=f"Whether to ignore the {config.fields.submit.name} attribute during validation")
    validate_parser.add_argument("--exit-early", action="store_true", help=f"If set, may exit without running all routines if any validation error is generated")

    args = parser.parse_args()
    logger.info(f"Subcommand function: {args.func.__name__}")
    exit(args.func(args))


if __name__ == "__main__":
    logger = logging.getLogger("quickvalidate")
    logger.info(f"==== STARTED {Path(__file__).name} at {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')} ====")
    main()

else:
    logger = logging.getLogger(__name__)